<!doctype html>
<html lang="zh-CN">
<head>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
    <meta name="referrer" content="no-referrer-when-downgrade">
    

    <title>Go语言基础之并发 | 早起的年轻人</title>
    <meta property="og:title" content="Go语言基础之并发 - 早起的年轻人">
    <meta property="og:type" content="article">
        
    <meta property="article:published_time" content='2021-09-03T23:10:11&#43;08:00'>
        
        
    <meta property="article:modified_time" content='2021-09-03T23:10:11&#43;08:00'>
        
    <meta name="Keywords" content="Flutter，golang,go语言,go语言笔记,飞雪无情,java,android,博客,项目管理,python,软件架构,公众号,小程序">
    <meta name="description" content="Go语言基础之并发">
        
    <meta name="author" content="luckly">
    <meta property="og:url" content="https://luckly.work/post/go_basic/go%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%B9%8B%E5%B9%B6%E5%8F%91/">
    <link rel="shortcut icon" href='/favicon.ico'  type="image/x-icon">

    <link rel="stylesheet" href='/css/normalize.css'>
    <link rel="stylesheet" href='/css/style.css'>
    <script type="text/javascript" src="//cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

    
    
    
        <link href="https://cdn.bootcdn.net/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.css" rel="stylesheet">
    
    
    
    
        <link rel="stylesheet" href='/css/douban.css'>
    
        <link rel="stylesheet" href='/css/other.css'>
    
</head>

<link rel="stylesheet"
      href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/highlight.min.js"></script>
<body>
    <header id="header" class="clearfix">
    <div class="container">
        <div class="col-group">
            <div class="site-name ">
                
                    <a id="logo" href="https://luckly.work/">
                        早起的年轻人
                    </a>
                
                <p class="description">专注于Flutter、Android、Java、Go语言(golang)、移动互联网、项目管理、软件架构</p>
            </div>
            <div>
                <nav id="nav-menu" class="clearfix">
                    <a class="current" href="https://luckly.work/">首页</a>
                    
                    <a  href="https://luckly.work/categories/" title="分类">分类</a>
                    
                    <a  href="https://luckly.work/tags/" title="标签">标签</a>
                    
                    <a  href="https://luckly.work/archives/" title="归档">归档</a>
                    
                    <a  href="https://luckly.work/about/" title="关于我">关于我</a>
                    
                    <a  href="https://github.com/ITmxs/" title="github">github</a>
                    
                </nav>
            </div>
        </div>
    </div>
</header>

    <div id="body">
        <div class="container">
            <div class="col-group">

                <div class="col-8" id="main">
                    
<div class="res-cons">
    <style type="text/css">
    .post-toc {
        position: fixed;
        width: 200px;
        margin-left: -210px;
        padding: 5px 10px;
        font-family: Athelas, STHeiti, Microsoft Yahei, serif;
        font-size: 12px;
        border: 1px solid rgba(0, 0, 0, .07);
        border-radius: 5px;
        background-color: rgba(255, 255, 255, 0.98);
        background-clip: padding-box;
        -webkit-box-shadow: 1px 1px 2px rgba(0, 0, 0, .125);
        box-shadow: 1px 1px 2px rgba(0, 0, 0, .125);
        word-wrap: break-word;
        white-space: nowrap;
        -webkit-box-sizing: border-box;
        box-sizing: border-box;
        z-index: 999;
        cursor: pointer;
        max-height: 70%;
        overflow-y: auto;
        overflow-x: hidden;
    }

    .post-toc .post-toc-title {
        width: 100%;
        margin: 0 auto;
        font-size: 20px;
        font-weight: 400;
        text-transform: uppercase;
        text-align: center;
    }

    .post-toc .post-toc-content {
        font-size: 15px;
    }

    .post-toc .post-toc-content>nav>ul {
        margin: 10px 0;
    }

    .post-toc .post-toc-content ul {
        padding-left: 20px;
        list-style: square;
        margin: 0.5em;
        line-height: 1.8em;
    }

    .post-toc .post-toc-content ul ul {
        padding-left: 15px;
        display: none;
    }

    @media print,
    screen and (max-width:1057px) {
        .post-toc {
            display: none;
        }
    }
</style>
<div class="post-toc" style="position: absolute; top: 188px;">
    <h2 class="post-toc-title">文章目录</h2>
    <div class="post-toc-content">
        <nav id="TableOfContents">
  <ul>
    <li><a href="#并发与并行">并发与并行</a></li>
    <li><a href="#goroutine">goroutine</a>
      <ul>
        <li><a href="#使用goroutine">使用goroutine</a></li>
        <li><a href="#启动单个goroutine">启动单个goroutine</a></li>
        <li><a href="#启动多个goroutine">启动多个goroutine</a></li>
      </ul>
    </li>
    <li><a href="#goroutine与线程">goroutine与线程</a>
      <ul>
        <li><a href="#可增长的栈">可增长的栈</a></li>
        <li><a href="#goroutine调度">goroutine调度</a></li>
        <li><a href="#gomaxprocs">GOMAXPROCS</a></li>
      </ul>
    </li>
    <li><a href="#channel">channel</a>
      <ul>
        <li><a href="#channel类型">channel类型</a></li>
        <li><a href="#创建channel">创建channel</a></li>
        <li><a href="#channel操作">channel操作</a></li>
        <li><a href="#无缓冲的通道">无缓冲的通道</a></li>
        <li><a href="#有缓冲的通道">有缓冲的通道</a></li>
        <li><a href="#for-range从通道循环取值">for range从通道循环取值</a></li>
        <li><a href="#单向通道">单向通道</a></li>
        <li><a href="#通道总结">通道总结</a></li>
      </ul>
    </li>
    <li><a href="#worker-poolgoroutine池">worker pool(goroutine池)</a></li>
    <li><a href="#select多路复用">select多路复用</a></li>
    <li><a href="#并发安全和锁">并发安全和锁</a>
      <ul>
        <li><a href="#互斥锁">互斥锁</a></li>
        <li><a href="#读写互斥锁">读写互斥锁</a></li>
        <li><a href="#syncwaitgroup">sync.WaitGroup</a></li>
        <li><a href="#synconce">sync.Once</a></li>
        <li><a href="#syncmap">sync.Map</a></li>
      </ul>
    </li>
    <li><a href="#原子操作">原子操作</a>
      <ul>
        <li><a href="#atomic包">atomic包</a></li>
        <li><a href="#示例">示例</a></li>
      </ul>
    </li>
  </ul>
</nav>
    </div>
</div>
<script type="text/javascript">
    $(document).ready(function () {
        var postToc = $(".post-toc");
        if (postToc.length) {
            var leftPos = $("#main").offset().left;
            if(leftPos<220){
                postToc.css({"width":leftPos-10,"margin-left":(0-leftPos)})
            }

            var t = postToc.offset().top - 20,
                a = {
                    start: {
                        position: "absolute",
                        top: t
                    },
                    process: {
                        position: "fixed",
                        top: 20
                    },
                };
            $(window).scroll(function () {
                var e = $(window).scrollTop();
                e < t ? postToc.css(a.start) : postToc.css(a.process)
            })
        }
    })
</script>
    <article class="post">
        <header>
            <h1 class="post-title">Go语言基础之并发</h1>
        </header>
        <date class="post-meta meta-date">
            2021年9月3日
        </date>
        
        <div class="post-meta">
            <span>|</span>
            
            <span class="meta-category"><a href='/categories/go'>go</a></span>
            
        </div>
        
        
        <div class="post-meta">
            <span id="busuanzi_container_page_pv">|<span id="busuanzi_value_page_pv"></span><span>
                    阅读</span></span>
        </div>
        
        
        <div class="post-content">
            <p>并发是编程里面一个非常重要的概念，Go语言在语言层面天生支持并发，这也是Go语言流行的一个很重要的原因。</p>
<h1 id="go语言中的并发编程">Go语言中的并发编程</h1>
<h2 id="并发与并行">并发与并行</h2>
<p>并发：同一时间段内执行多个任务(你在用微信和两个女朋友聊天)。</p>
<p>并行：同一时刻执行多个任务(你和你朋友都在用微信和女朋友聊天)。</p>
<p>Go语言的并发通过<code>goroutine</code>实现。<code>goroutine</code>类似于线程，属于用户态的线程，我们可以根据需要创建成千上万个<code>goroutine</code>并发工作。<code>goroutine</code>是由Go语言的运行时(runtime)调度完成，而线程是由操作系统调度完成。</p>
<p>Go语言还提供<code>channel</code>在多个<code>goroutine</code>间进行通信。<code>goroutine</code>和<code>channel</code>是 Go 语言秉承的 CSP(Communicating Sequential Process)并发模式的重要实现基础。</p>
<h2 id="goroutine">goroutine</h2>
<p>在java/c++中我们要实现并发编程的时候，我们通常需要自己维护一个线程池，并且需要自己去包装一个又一个的任务，同时需要自己去调度线程执行任务并维护上下文切换，这一切通常会耗费程序员大量的心智。那么能不能有一种机制，程序员只需要定义很多个任务，让系统去帮助我们把这些任务分配到CPU上实现并发执行呢？</p>
<p>Go语言中的<code>goroutine</code>就是这样一种机制，<code>goroutine</code>的概念类似于线程，但 <code>goroutine</code>是由Go的运行时(runtime)调度和管理的。Go程序会智能地将 goroutine 中的任务合理地分配给每个CPU。Go语言之所以被称为现代化的编程语言，就是因为它在语言层面已经内置了调度和上下文切换的机制。</p>
<p>在Go语言编程中你不需要去自己写进程、线程、协程，你的技能包里只有一个技能–<code>goroutine</code>，当你需要让某个任务并发执行的时候，你只需要把这个任务包装成一个函数，开启一个<code>goroutine</code>去执行这个函数就可以了，就是这么简单粗暴。</p>
<h3 id="使用goroutine">使用goroutine</h3>
<p>Go语言中使用<code>goroutine</code>非常简单，只需要在调用函数的时候在前面加上<code>go</code>关键字，就可以为一个函数创建一个<code>goroutine</code>。</p>
<p>一个<code>goroutine</code>必定对应一个函数，可以创建多个<code>goroutine</code>去执行相同的函数。</p>
<h3 id="启动单个goroutine">启动单个goroutine</h3>
<p>启动goroutine的方式非常简单，只需要在调用的函数(普通函数和匿名函数)前面加上一个<code>go</code>关键字。</p>
<p>举个例子如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">hello</span>() {
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Hello Goroutine!&#34;</span>)
}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">hello</span>()
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;main goroutine done!&#34;</span>)
}
</code></pre></div><p>这个示例中hello函数和下面的语句是串行的，执行的结果是打印完<code>Hello Goroutine!</code>后打印<code>main goroutine done!</code>。</p>
<p>接下来我们在调用hello函数前面加上关键字<code>go</code>，也就是启动一个goroutine去执行hello这个函数。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">hello</span>() <span style="color:#75715e">// 启动另外一个goroutine去执行hello函数
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;main goroutine done!&#34;</span>)
}
</code></pre></div><p>这一次的执行结果只打印了<code>main goroutine done!</code>，并没有打印<code>Hello Goroutine!</code>。为什么呢？</p>
<p>在程序启动时，Go程序就会为<code>main()</code>函数创建一个默认的<code>goroutine</code>。</p>
<p>当main()函数返回的时候该<code>goroutine</code>就结束了，所有在<code>main()</code>函数中启动的<code>goroutine</code>会一同结束，<code>main</code>函数所在的<code>goroutine</code>就像是权利的游戏中的夜王，其他的<code>goroutine</code>都是异鬼，夜王一死它转化的那些异鬼也就全部GG了。</p>
<p>所以我们要想办法让main函数等一等hello函数，最简单粗暴的方式就是<code>time.Sleep</code>了。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">hello</span>() <span style="color:#75715e">// 启动另外一个goroutine去执行hello函数
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;main goroutine done!&#34;</span>)
	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
}
</code></pre></div><p>执行上面的代码你会发现，这一次先打印<code>main goroutine done!</code>，然后紧接着打印<code>Hello Goroutine!</code>。</p>
<p>首先为什么会先打印<code>main goroutine done!</code>是因为我们在创建新的goroutine的时候需要花费一些时间，而此时main函数所在的<code>goroutine</code>是继续执行的。</p>
<h3 id="启动多个goroutine">启动多个goroutine</h3>
<p>在Go语言中实现并发就是这样简单，我们还可以启动多个<code>goroutine</code>。让我们再来一个例子： (这里使用了<code>sync.WaitGroup</code>来实现goroutine的同步)</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">hello</span>(<span style="color:#a6e22e">i</span> <span style="color:#66d9ef">int</span>) {
	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>() <span style="color:#75715e">// goroutine结束就登记-1
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Hello Goroutine!&#34;</span>, <span style="color:#a6e22e">i</span>)
}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {

	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">10</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>) <span style="color:#75715e">// 启动一个goroutine就登记+1
</span><span style="color:#75715e"></span>		<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">hello</span>(<span style="color:#a6e22e">i</span>)
	}
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>() <span style="color:#75715e">// 等待所有登记的goroutine都结束
</span><span style="color:#75715e"></span>}
</code></pre></div><p>多次执行上面的代码，会发现每次打印的数字的顺序都不一致。这是因为10个<code>goroutine</code>是并发执行的，而<code>goroutine</code>的调度是随机的。</p>
<h2 id="goroutine与线程">goroutine与线程</h2>
<h3 id="可增长的栈">可增长的栈</h3>
<p>OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个<code>goroutine</code>的栈在其生命周期开始时只有很小的栈(典型情况下2KB)，<code>goroutine</code>的栈不是固定的，他可以按需增大和缩小，<code>goroutine</code>的栈大小限制可以达到1GB，虽然极少会用到这么大。所以在Go语言中一次创建十万左右的<code>goroutine</code>也是可以的。</p>
<h3 id="goroutine调度">goroutine调度</h3>
<p><code>GPM</code>是Go语言运行时(runtime)层面的实现，是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。</p>
<ul>
<li><code>G</code>很好理解，就是个goroutine的，里面除了存放本goroutine信息外 还有与所在P的绑定等信息。</li>
<li><code>P</code>管理着一组goroutine队列，P里面会存储当前goroutine运行的上下文环境(函数指针，堆栈地址及地址边界)，P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取，如果全局队列里也消费完了会去其他P的队列里抢任务。</li>
<li><code>M(machine)</code>是Go运行时(runtime)对操作系统内核线程的虚拟， M与内核线程一般是一一映射的关系， 一个groutine最终是要放到M上执行的；</li>
</ul>
<p>P与M一般也是一一对应的。他们关系是： P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时，runtime会新建一个M，阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M。</p>
<p>P的个数是通过<code>runtime.GOMAXPROCS</code>设定(最大256)，Go1.5版本之后默认为物理线程数。 在并发量大的时候会增加一些P和M，但不会太多，切换太频繁的话得不偿失。</p>
<p>单从线程调度讲，Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的，<code>goroutine</code>则是由Go运行时(runtime)自己的调度器调度的，这个调度器使用一个称为m:n调度的技术(复用/调度m个goroutine到n个OS线程)。 其一大特点是goroutine的调度是在用户态下完成的， 不涉及内核态与用户态之间的频繁切换，包括内存的分配与释放，都是在用户态维护着一块大的内存池， 不直接调用系统的malloc函数(除非内存池需要改变)，成本比调度OS线程低很多。 另一方面充分利用了多核的硬件资源，近似的把若干goroutine均分在物理线程上， 再加上本身goroutine的超轻量，以上种种保证了go调度方面的性能。</p>
<p><a href="https://www.cnblogs.com/sunsky303/p/9705727.html">点我了解更多</a></p>
<h3 id="gomaxprocs">GOMAXPROCS</h3>
<p>Go运行时的调度器使用<code>GOMAXPROCS</code>参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上，调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)。</p>
<p>Go语言中可以通过<code>runtime.GOMAXPROCS()</code>函数设置当前程序并发时占用的CPU逻辑核心数。</p>
<p>Go1.5版本之前，默认使用的是单核心执行。Go1.5版本之后，默认使用全部的CPU逻辑核心数。</p>
<p>我们可以通过将任务分配到不同的CPU逻辑核心上实现并行的效果，这里举个例子：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">a</span>() {
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">10</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;A:&#34;</span>, <span style="color:#a6e22e">i</span>)
	}
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">b</span>() {
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">10</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;B:&#34;</span>, <span style="color:#a6e22e">i</span>)
	}
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">GOMAXPROCS</span>(<span style="color:#ae81ff">1</span>)
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">a</span>()
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">b</span>()
	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
}
</code></pre></div><p>两个任务只有一个逻辑核心，此时是做完一个任务再做另一个任务。 将逻辑核心数设为2，此时两个任务并行执行，代码如下。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">a</span>() {
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">10</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;A:&#34;</span>, <span style="color:#a6e22e">i</span>)
	}
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">b</span>() {
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">10</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;B:&#34;</span>, <span style="color:#a6e22e">i</span>)
	}
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">GOMAXPROCS</span>(<span style="color:#ae81ff">2</span>)
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">a</span>()
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">b</span>()
	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
}
</code></pre></div><p>Go语言中的操作系统线程和goroutine的关系：</p>
<ol>
<li>一个操作系统线程对应用户态多个goroutine。</li>
<li>go程序可以同时使用多个操作系统线程。</li>
<li>goroutine和OS线程是多对多的关系，即m:n。</li>
</ol>
<h2 id="channel">channel</h2>
<p>单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。</p>
<p>虽然可以使用共享内存进行数据交换，但是共享内存在不同的<code>goroutine</code>中容易发生竞态问题。为了保证数据交换的正确性，必须使用互斥量对内存进行加锁，这种做法势必造成性能问题。</p>
<p>Go语言的并发模型是<code>CSP(Communicating Sequential Processes)</code>，提倡<strong>通过通信共享内存</strong>而不是<strong>通过共享内存而实现通信</strong>。</p>
<p>如果说<code>goroutine</code>是Go程序并发的执行体，<code>channel</code>就是它们之间的连接。<code>channel</code>是可以让一个<code>goroutine</code>发送特定值到另一个<code>goroutine</code>的通信机制。</p>
<p>Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列，总是遵循先入先出(First In First Out)的规则，保证收发数据的顺序。每一个通道都是一个具体类型的导管，也就是声明channel的时候需要为其指定元素类型。</p>
<h3 id="channel类型">channel类型</h3>
<p><code>channel</code>是一种类型，一种引用类型。声明通道类型的格式如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">变量</span> <span style="color:#66d9ef">chan</span> <span style="color:#a6e22e">元素类型</span>
</code></pre></div><p>举几个例子：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ch1</span> <span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>   <span style="color:#75715e">// 声明一个传递整型的通道
</span><span style="color:#75715e"></span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ch2</span> <span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">bool</span>  <span style="color:#75715e">// 声明一个传递布尔型的通道
</span><span style="color:#75715e"></span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ch3</span> <span style="color:#66d9ef">chan</span> []<span style="color:#66d9ef">int</span> <span style="color:#75715e">// 声明一个传递int切片的通道
</span></code></pre></div><h3 id="创建channel">创建channel</h3>
<p>通道是引用类型，通道类型的空值是<code>nil</code>。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ch</span> <span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">ch</span>) <span style="color:#75715e">// &lt;nil&gt;
</span></code></pre></div><p>声明的通道后需要使用<code>make</code>函数初始化之后才能使用。</p>
<p>创建channel的格式如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go">make(<span style="color:#66d9ef">chan</span> <span style="color:#a6e22e">元素类型</span>, [<span style="color:#a6e22e">缓冲大小</span>])
</code></pre></div><p>channel的缓冲大小是可选的。</p>
<p>举几个例子：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#a6e22e">ch4</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>)
<span style="color:#a6e22e">ch5</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">bool</span>)
<span style="color:#a6e22e">ch6</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> []<span style="color:#66d9ef">int</span>)
</code></pre></div><h3 id="channel操作">channel操作</h3>
<p>通道有发送(send)、接收(receive)和关闭(close)三种操作。</p>
<p>发送和接收都使用<code>&lt;-</code>符号。</p>
<p>现在我们先使用以下语句定义一个通道：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#a6e22e">ch</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>)
</code></pre></div><h4 id="发送">发送</h4>
<p>将一个值发送到通道中。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#a6e22e">ch</span> <span style="color:#f92672">&lt;-</span> <span style="color:#ae81ff">10</span> <span style="color:#75715e">// 把10发送到ch中
</span></code></pre></div><h4 id="接收">接收</h4>
<p>从一个通道中接收值。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#a6e22e">x</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">ch</span> <span style="color:#75715e">// 从ch中接收值并赋值给变量x
</span><span style="color:#75715e"></span><span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ch</span>       <span style="color:#75715e">// 从ch中接收值，忽略结果
</span></code></pre></div><h4 id="关闭">关闭</h4>
<p>我们通过调用内置的<code>close</code>函数来关闭通道。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go">close(<span style="color:#a6e22e">ch</span>)
</code></pre></div><p>关于关闭通道需要注意的事情是，只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的，它和关闭文件是不一样的，在结束操作之后关闭文件是必须要做的，但关闭通道不是必须的。</p>
<p>关闭后的通道有以下特点：</p>
<ol>
<li>对一个关闭的通道再发送值就会导致panic。</li>
<li>对一个关闭的通道进行接收会一直获取值直到通道为空。</li>
<li>对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。</li>
<li>关闭一个已经关闭的通道会导致panic。</li>
</ol>
<h3 id="无缓冲的通道">无缓冲的通道</h3>
<p>无缓冲的通道又称为阻塞的通道。我们来看一下下面的代码：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">ch</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>)
	<span style="color:#a6e22e">ch</span> <span style="color:#f92672">&lt;-</span> <span style="color:#ae81ff">10</span>
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;发送成功&#34;</span>)
}
</code></pre></div><p>上面这段代码能够通过编译，但是执行的时候会出现以下错误：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">fatal error: all goroutines are asleep - deadlock!

goroutine <span style="color:#ae81ff">1</span> <span style="color:#f92672">[</span>chan send<span style="color:#f92672">]</span>:
main.main<span style="color:#f92672">()</span>
        .../src/github.com/Q1mi/studygo/day06/channel02/main.go:8 +0x54
</code></pre></div><p>为什么会出现<code>deadlock</code>错误呢？</p>
<p>因为我们使用<code>ch := make(chan int)</code>创建的是无缓冲的通道，无缓冲的通道只有在有人接收值的时候才能发送值。就像你住的小区没有快递柜和代收点，快递员给你打电话必须要把这个物品送到你的手中，简单来说就是无缓冲的通道必须有接收才能发送。</p>
<p>上面的代码会阻塞在<code>ch &lt;- 10</code>这一行代码形成死锁，那如何解决这个问题呢？</p>
<p>一种方法是启用一个<code>goroutine</code>去接收值，例如：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">recv</span>(<span style="color:#a6e22e">c</span> <span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>) {
	<span style="color:#a6e22e">ret</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">c</span>
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;接收成功&#34;</span>, <span style="color:#a6e22e">ret</span>)
}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">ch</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>)
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">recv</span>(<span style="color:#a6e22e">ch</span>) <span style="color:#75715e">// 启用goroutine从通道接收值
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">ch</span> <span style="color:#f92672">&lt;-</span> <span style="color:#ae81ff">10</span>
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;发送成功&#34;</span>)
}
</code></pre></div><p>无缓冲通道上的发送操作会阻塞，直到另一个<code>goroutine</code>在该通道上执行接收操作，这时值才能发送成功，两个<code>goroutine</code>将继续执行。相反，如果接收操作先执行，接收方的goroutine将阻塞，直到另一个<code>goroutine</code>在该通道上发送一个值。</p>
<p>使用无缓冲通道进行通信将导致发送和接收的<code>goroutine</code>同步化。因此，无缓冲通道也被称为<code>同步通道</code>。</p>
<h3 id="有缓冲的通道">有缓冲的通道</h3>
<p>解决上面问题的方法还有一种就是使用有缓冲区的通道。我们可以在使用make函数初始化通道的时候为其指定通道的容量，例如：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">ch</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>, <span style="color:#ae81ff">1</span>) <span style="color:#75715e">// 创建一个容量为1的有缓冲区通道
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">ch</span> <span style="color:#f92672">&lt;-</span> <span style="color:#ae81ff">10</span>
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;发送成功&#34;</span>)
}
</code></pre></div><p>只要通道的容量大于零，那么该通道就是有缓冲的通道，通道的容量表示通道中能存放元素的数量。就像你小区的快递柜只有那么个多格子，格子满了就装不下了，就阻塞了，等到别人取走一个快递员就能往里面放一个。</p>
<p>我们可以使用内置的<code>len</code>函数获取通道内元素的数量，使用<code>cap</code>函数获取通道的容量，虽然我们很少会这么做。</p>
<h3 id="for-range从通道循环取值">for range从通道循环取值</h3>
<p>当向通道中发送完数据时，我们可以通过<code>close</code>函数来关闭通道。</p>
<p>当通道被关闭时，再往该通道发送值会引发<code>panic</code>，从该通道取值的操作会先取完通道中的值，再然后取到的值一直都是对应类型的零值。那如何判断一个通道是否被关闭了呢？</p>
<p>我们来看下面这个例子：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">// channel 练习
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">ch1</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>)
	<span style="color:#a6e22e">ch2</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>)
	<span style="color:#75715e">// 开启goroutine将0~100的数发送到ch1中
</span><span style="color:#75715e"></span>	<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
		<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">100</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
			<span style="color:#a6e22e">ch1</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">i</span>
		}
		close(<span style="color:#a6e22e">ch1</span>)
	}()
	<span style="color:#75715e">// 开启goroutine从ch1中接收值，并将该值的平方发送到ch2中
</span><span style="color:#75715e"></span>	<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
		<span style="color:#66d9ef">for</span> {
			<span style="color:#a6e22e">i</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ch1</span> <span style="color:#75715e">// 通道关闭后再取值ok=false
</span><span style="color:#75715e"></span>			<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ok</span> {
				<span style="color:#66d9ef">break</span>
			}
			<span style="color:#a6e22e">ch2</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">i</span>
		}
		close(<span style="color:#a6e22e">ch2</span>)
	}()
	<span style="color:#75715e">// 在主goroutine中从ch2中接收值打印
</span><span style="color:#75715e"></span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">ch2</span> { <span style="color:#75715e">// 通道关闭后会退出for range循环
</span><span style="color:#75715e"></span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">i</span>)
	}
}
</code></pre></div><p>从上面的例子中我们看到有两种方式在接收值的时候判断该通道是否被关闭，不过我们通常使用的是<code>for range</code>的方式。使用<code>for range</code>遍历通道，当通道被关闭的时候就会退出<code>for range</code>。</p>
<h3 id="单向通道">单向通道</h3>
<p>有的时候我们会将通道作为参数在多个任务函数间传递，很多时候我们在不同的任务函数中使用通道都会对其进行限制，比如限制通道在函数中只能发送或只能接收。</p>
<p>Go语言中提供了<strong>单向通道</strong>来处理这种情况。例如，我们把上面的例子改造如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">counter</span>(<span style="color:#a6e22e">out</span> <span style="color:#66d9ef">chan</span><span style="color:#f92672">&lt;-</span> <span style="color:#66d9ef">int</span>) {
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">100</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">out</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">i</span>
	}
	close(<span style="color:#a6e22e">out</span>)
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">squarer</span>(<span style="color:#a6e22e">out</span> <span style="color:#66d9ef">chan</span><span style="color:#f92672">&lt;-</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">in</span> <span style="color:#f92672">&lt;-</span><span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>) {
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">in</span> {
		<span style="color:#a6e22e">out</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">i</span>
	}
	close(<span style="color:#a6e22e">out</span>)
}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">printer</span>(<span style="color:#a6e22e">in</span> <span style="color:#f92672">&lt;-</span><span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>) {
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">in</span> {
		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">i</span>)
	}
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">ch1</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>)
	<span style="color:#a6e22e">ch2</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>)
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">counter</span>(<span style="color:#a6e22e">ch1</span>)
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">squarer</span>(<span style="color:#a6e22e">ch2</span>, <span style="color:#a6e22e">ch1</span>)
	<span style="color:#a6e22e">printer</span>(<span style="color:#a6e22e">ch2</span>)
}
</code></pre></div><p>其中，</p>
<ul>
<li><code>chan&lt;- int</code>是一个只写单向通道(只能对其写入int类型值)，可以对其执行发送操作但是不能执行接收操作；</li>
<li><code>&lt;-chan int</code>是一个只读单向通道(只能从其读取int类型值)，可以对其执行接收操作但是不能执行发送操作。</li>
</ul>
<p>在函数传参及任何赋值操作中可以将双向通道转换为单向通道，但反过来是不可以的。</p>
<h3 id="通道总结">通道总结</h3>
<p><code>channel</code>常见的异常总结，如下图：
        <a data-fancybox="gallery" href="https://gitee.com/itmxs/images/raw/master/img/channel01.png">
            <img class="mx-auto" alt="channel异常总结" src="https://gitee.com/itmxs/images/raw/master/img/channel01.png" />
        </a>
    </p>
<p>关闭已经关闭的<code>channel</code>也会引发<code>panic</code>。</p>
<h2 id="worker-poolgoroutine池">worker pool(goroutine池)</h2>
<p>在工作中我们通常会使用可以指定启动的goroutine数量–<code>worker pool</code>模式，控制<code>goroutine</code>的数量，防止<code>goroutine</code>泄漏和暴涨。</p>
<p>一个简易的<code>work pool</code>示例代码如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">worker</span>(<span style="color:#a6e22e">id</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">jobs</span> <span style="color:#f92672">&lt;-</span><span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">results</span> <span style="color:#66d9ef">chan</span><span style="color:#f92672">&lt;-</span> <span style="color:#66d9ef">int</span>) {
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">j</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">jobs</span> {
		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;worker:%d start job:%d\n&#34;</span>, <span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">j</span>)
		<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;worker:%d end job:%d\n&#34;</span>, <span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">j</span>)
		<span style="color:#a6e22e">results</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">j</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span>
	}
}


<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">jobs</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>, <span style="color:#ae81ff">100</span>)
	<span style="color:#a6e22e">results</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>, <span style="color:#ae81ff">100</span>)
	<span style="color:#75715e">// 开启3个goroutine
</span><span style="color:#75715e"></span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">w</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">w</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">3</span>; <span style="color:#a6e22e">w</span><span style="color:#f92672">++</span> {
		<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">worker</span>(<span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">jobs</span>, <span style="color:#a6e22e">results</span>)
	}
	<span style="color:#75715e">// 5个任务
</span><span style="color:#75715e"></span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">j</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">j</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">5</span>; <span style="color:#a6e22e">j</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">jobs</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">j</span>
	}
	close(<span style="color:#a6e22e">jobs</span>)
	<span style="color:#75715e">// 输出结果
</span><span style="color:#75715e"></span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">a</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">a</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">5</span>; <span style="color:#a6e22e">a</span><span style="color:#f92672">++</span> {
		<span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">results</span>
	}
}
</code></pre></div><h2 id="select多路复用">select多路复用</h2>
<p>在某些场景下我们需要同时从多个通道接收数据。通道在接收数据时，如果没有数据可以接收将会发生阻塞。你也许会写出如下代码使用遍历的方式来实现：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">for</span>{
    <span style="color:#75715e">// 尝试从ch1接收值
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">data</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ch1</span>
    <span style="color:#75715e">// 尝试从ch2接收值
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">data</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ch2</span>
    <span style="color:#960050;background-color:#1e0010">…</span>
}
</code></pre></div><p>这种方式虽然可以实现从多个通道接收值的需求，但是运行性能会差很多。为了应对这种场景，Go内置了<code>select</code>关键字，可以同时响应多个通道的操作。</p>
<p><code>select</code>的使用类似于switch语句，它有一系列case分支和一个默认的分支。每个case会对应一个通道的通信(接收或发送)过程。<code>select</code>会一直等待，直到某个<code>case</code>的通信操作完成时，就会执行<code>case</code>分支对应的语句。具体格式如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">select</span>{
    <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ch1</span>:
        <span style="color:#f92672">...</span>
    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">data</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ch2</span>:
        <span style="color:#f92672">...</span>
    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">ch3</span><span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">data</span>:
        <span style="color:#f92672">...</span>
    <span style="color:#66d9ef">default</span>:
        <span style="color:#a6e22e">默认操作</span>
}
</code></pre></div><p>举个小例子来演示下<code>select</code>的使用：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">ch</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>, <span style="color:#ae81ff">1</span>)
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">10</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#66d9ef">select</span> {
		<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">x</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ch</span>:
			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">x</span>)
		<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">ch</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">i</span>:
		}
	}
}
</code></pre></div><p>使用<code>select</code>语句能提高代码的可读性。</p>
<ul>
<li>可处理一个或多个channel的发送/接收操作。</li>
<li>如果多个<code>case</code>同时满足，<code>select</code>会随机选择一个。</li>
<li>对于没有<code>case</code>的<code>select{}</code>会一直等待，可用于阻塞main函数。</li>
</ul>
<h2 id="并发安全和锁">并发安全和锁</h2>
<p>有时候在Go代码中可能会存在多个<code>goroutine</code>同时操作一个资源(临界区)，这种情况会发生<code>竞态问题</code>(数据竞态)。类比现实生活中的例子有十字路口被各个方向的的汽车竞争；还有火车上的卫生间被车厢里的人竞争。</p>
<p>举个例子：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">x</span> <span style="color:#66d9ef">int64</span>
<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">add</span>() {
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">5000</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">x</span> = <span style="color:#a6e22e">x</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>
	}
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">2</span>)
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">add</span>()
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">add</span>()
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">x</span>)
}
</code></pre></div><p>上面的代码中我们开启了两个<code>goroutine</code>去累加变量x的值，这两个<code>goroutine</code>在访问和修改<code>x</code>变量的时候就会存在数据竞争，导致最后的结果与期待的不符。</p>
<h3 id="互斥锁">互斥锁</h3>
<p>互斥锁是一种常用的控制共享资源访问的方法，它能够保证同时只有一个<code>goroutine</code>可以访问共享资源。Go语言中使用<code>sync</code>包的<code>Mutex</code>类型来实现互斥锁。 使用互斥锁来修复上面代码的问题：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">x</span> <span style="color:#66d9ef">int64</span>
<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">lock</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Mutex</span>

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">add</span>() {
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">5000</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Lock</span>() <span style="color:#75715e">// 加锁
</span><span style="color:#75715e"></span>		<span style="color:#a6e22e">x</span> = <span style="color:#a6e22e">x</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>
		<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Unlock</span>() <span style="color:#75715e">// 解锁
</span><span style="color:#75715e"></span>	}
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">2</span>)
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">add</span>()
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">add</span>()
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">x</span>)
}
</code></pre></div><p>使用互斥锁能够保证同一时间有且只有一个<code>goroutine</code>进入临界区，其他的<code>goroutine</code>则在等待锁；当互斥锁释放后，等待的<code>goroutine</code>才可以获取锁进入临界区，多个<code>goroutine</code>同时等待一个锁时，唤醒的策略是随机的。</p>
<h3 id="读写互斥锁">读写互斥锁</h3>
<p>互斥锁是完全互斥的，但是有很多实际的场景下是读多写少的，当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的，这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用<code>sync</code>包中的<code>RWMutex</code>类型。</p>
<p>读写锁分为两种：读锁和写锁。当一个goroutine获取读锁之后，其他的<code>goroutine</code>如果是获取读锁会继续获得锁，如果是获取写锁就会等待；当一个<code>goroutine</code>获取写锁之后，其他的<code>goroutine</code>无论是获取读锁还是写锁都会等待。</p>
<p>读写锁示例：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> (
	<span style="color:#a6e22e">x</span>      <span style="color:#66d9ef">int64</span>
	<span style="color:#a6e22e">wg</span>     <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
	<span style="color:#a6e22e">lock</span>   <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Mutex</span>
	<span style="color:#a6e22e">rwlock</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">RWMutex</span>
)

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">write</span>() {
	<span style="color:#75715e">// lock.Lock()   // 加互斥锁
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">rwlock</span>.<span style="color:#a6e22e">Lock</span>() <span style="color:#75715e">// 加写锁
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">x</span> = <span style="color:#a6e22e">x</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>
	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">10</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>) <span style="color:#75715e">// 假设读操作耗时10毫秒
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">rwlock</span>.<span style="color:#a6e22e">Unlock</span>()                   <span style="color:#75715e">// 解写锁
</span><span style="color:#75715e"></span>	<span style="color:#75715e">// lock.Unlock()                     // 解互斥锁
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">read</span>() {
	<span style="color:#75715e">// lock.Lock()                  // 加互斥锁
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">rwlock</span>.<span style="color:#a6e22e">RLock</span>()               <span style="color:#75715e">// 加读锁
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>) <span style="color:#75715e">// 假设读操作耗时1毫秒
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">rwlock</span>.<span style="color:#a6e22e">RUnlock</span>()             <span style="color:#75715e">// 解读锁
</span><span style="color:#75715e"></span>	<span style="color:#75715e">// lock.Unlock()                // 解互斥锁
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">start</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">10</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
		<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">write</span>()
	}

	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">1000</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
		<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">read</span>()
	}

	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
	<span style="color:#a6e22e">end</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">end</span>.<span style="color:#a6e22e">Sub</span>(<span style="color:#a6e22e">start</span>))
}
</code></pre></div><p>需要注意的是读写锁非常适合读多写少的场景，如果读和写的操作差别不大，读写锁的优势就发挥不出来。</p>
<h3 id="syncwaitgroup">sync.WaitGroup</h3>
<p>在代码中生硬的使用<code>time.Sleep</code>肯定是不合适的，Go语言中可以使用<code>sync.WaitGroup</code>来实现并发任务的同步。 <code>sync.WaitGroup</code>有以下几个方法：</p>
<table>
<thead>
<tr>
<th style="text-align:center">方法名</th>
<th style="text-align:center">功能</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">(wg * WaitGroup) Add(delta int)</td>
<td style="text-align:center">计数器+delta</td>
</tr>
<tr>
<td style="text-align:center">(wg *WaitGroup) Done()</td>
<td style="text-align:center">计数器-1</td>
</tr>
<tr>
<td style="text-align:center">(wg *WaitGroup) Wait()</td>
<td style="text-align:center">阻塞直到计数器变为0</td>
</tr>
</tbody>
</table>
<p><code>sync.WaitGroup</code>内部维护着一个计数器，计数器的值可以增加和减少。例如当我们启动了N 个并发任务时，就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完，当计数器值为0时，表示所有并发任务已经完成。</p>
<p>我们利用<code>sync.WaitGroup</code>将上面的代码优化一下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">hello</span>() {
	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Hello Goroutine!&#34;</span>)
}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">hello</span>() <span style="color:#75715e">// 启动另外一个goroutine去执行hello函数
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;main goroutine done!&#34;</span>)
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
}
</code></pre></div><p>需要注意<code>sync.WaitGroup</code>是一个结构体，传递的时候要传递指针。</p>
<h3 id="synconce">sync.Once</h3>
<p>说在前面的话：这是一个进阶知识点。</p>
<p>在编程的很多场景下我们需要确保某些操作在高并发的场景下只执行一次，例如只加载一次配置文件、只关闭一次通道等。</p>
<p>Go语言中的<code>sync</code>包中提供了一个针对只执行一次场景的解决方案–<code>sync.Once</code>。</p>
<p><code>sync.Once</code>只有一个<code>Do</code>方法，其签名如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">o</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Once</span>) <span style="color:#a6e22e">Do</span>(<span style="color:#a6e22e">f</span> <span style="color:#66d9ef">func</span>()) {}
</code></pre></div><p><em>备注：如果要执行的函数<code>f</code>需要传递参数就需要搭配闭包来使用。</em></p>
<h4 id="加载配置文件示例">加载配置文件示例</h4>
<p>延迟一个开销很大的初始化操作到真正用到它的时候再执行是一个很好的实践。因为预先初始化一个变量(比如在init函数中完成初始化)会增加程序的启动耗时，而且有可能实际执行过程中这个变量没有用上，那么这个初始化操作就不是必须要做的。我们来看一个例子：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">icons</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#a6e22e">image</span>.<span style="color:#a6e22e">Image</span>

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">loadIcons</span>() {
	<span style="color:#a6e22e">icons</span> = <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#a6e22e">image</span>.<span style="color:#a6e22e">Image</span>{
		<span style="color:#e6db74">&#34;left&#34;</span>:  <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;left.png&#34;</span>),
		<span style="color:#e6db74">&#34;up&#34;</span>:    <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;up.png&#34;</span>),
		<span style="color:#e6db74">&#34;right&#34;</span>: <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;right.png&#34;</span>),
		<span style="color:#e6db74">&#34;down&#34;</span>:  <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;down.png&#34;</span>),
	}
}

<span style="color:#75715e">// Icon 被多个goroutine调用时不是并发安全的
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Icon</span>(<span style="color:#a6e22e">name</span> <span style="color:#66d9ef">string</span>) <span style="color:#a6e22e">image</span>.<span style="color:#a6e22e">Image</span> {
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">icons</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
		<span style="color:#a6e22e">loadIcons</span>()
	}
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">icons</span>[<span style="color:#a6e22e">name</span>]
}
</code></pre></div><p>多个<code>goroutine</code>并发调用Icon函数时不是并发安全的，现代的编译器和CPU可能会在保证每个<code>goroutine</code>都满足串行一致的基础上自由地重排访问内存的顺序。loadIcons函数可能会被重排为以下结果：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">loadIcons</span>() {
	<span style="color:#a6e22e">icons</span> = make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#a6e22e">image</span>.<span style="color:#a6e22e">Image</span>)
	<span style="color:#a6e22e">icons</span>[<span style="color:#e6db74">&#34;left&#34;</span>] = <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;left.png&#34;</span>)
	<span style="color:#a6e22e">icons</span>[<span style="color:#e6db74">&#34;up&#34;</span>] = <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;up.png&#34;</span>)
	<span style="color:#a6e22e">icons</span>[<span style="color:#e6db74">&#34;right&#34;</span>] = <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;right.png&#34;</span>)
	<span style="color:#a6e22e">icons</span>[<span style="color:#e6db74">&#34;down&#34;</span>] = <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;down.png&#34;</span>)
}
</code></pre></div><p>在这种情况下就会出现即使判断了<code>icons</code>不是nil也不意味着变量初始化完成了。考虑到这种情况，我们能想到的办法就是添加互斥锁，保证初始化<code>icons</code>的时候不会被其他的<code>goroutine</code>操作，但是这样做又会引发性能问题。</p>
<p>使用<code>sync.Once</code>改造的示例代码如下：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">icons</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#a6e22e">image</span>.<span style="color:#a6e22e">Image</span>

<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">loadIconsOnce</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Once</span>

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">loadIcons</span>() {
	<span style="color:#a6e22e">icons</span> = <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#a6e22e">image</span>.<span style="color:#a6e22e">Image</span>{
		<span style="color:#e6db74">&#34;left&#34;</span>:  <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;left.png&#34;</span>),
		<span style="color:#e6db74">&#34;up&#34;</span>:    <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;up.png&#34;</span>),
		<span style="color:#e6db74">&#34;right&#34;</span>: <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;right.png&#34;</span>),
		<span style="color:#e6db74">&#34;down&#34;</span>:  <span style="color:#a6e22e">loadIcon</span>(<span style="color:#e6db74">&#34;down.png&#34;</span>),
	}
}

<span style="color:#75715e">// Icon 是并发安全的
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Icon</span>(<span style="color:#a6e22e">name</span> <span style="color:#66d9ef">string</span>) <span style="color:#a6e22e">image</span>.<span style="color:#a6e22e">Image</span> {
	<span style="color:#a6e22e">loadIconsOnce</span>.<span style="color:#a6e22e">Do</span>(<span style="color:#a6e22e">loadIcons</span>)
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">icons</span>[<span style="color:#a6e22e">name</span>]
}
</code></pre></div><h4 id="并发安全的单例模式">并发安全的单例模式</h4>
<p>下面是借助<code>sync.Once</code>实现的并发安全的单例模式：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#f92672">package</span> <span style="color:#a6e22e">singleton</span>

<span style="color:#f92672">import</span> (
    <span style="color:#e6db74">&#34;sync&#34;</span>
)

<span style="color:#66d9ef">type</span> <span style="color:#a6e22e">singleton</span> <span style="color:#66d9ef">struct</span> {}

<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">instance</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">singleton</span>
<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">once</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Once</span>

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">GetInstance</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">singleton</span> {
    <span style="color:#a6e22e">once</span>.<span style="color:#a6e22e">Do</span>(<span style="color:#66d9ef">func</span>() {
        <span style="color:#a6e22e">instance</span> = <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">singleton</span>{}
    })
    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">instance</span>
}
</code></pre></div><p><code>sync.Once</code>其实内部包含一个互斥锁和一个布尔值，互斥锁保证布尔值和数据的安全，而布尔值用来记录初始化是否完成。这样设计就能保证初始化操作的时候是并发安全的并且初始化操作也不会被执行多次。</p>
<h3 id="syncmap">sync.Map</h3>
<p>Go语言中内置的map不是并发安全的。请看下面的示例：</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">m</span> = make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#66d9ef">int</span>)

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">get</span>(<span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">int</span> {
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">m</span>[<span style="color:#a6e22e">key</span>]
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">set</span>(<span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">value</span> <span style="color:#66d9ef">int</span>) {
	<span style="color:#a6e22e">m</span>[<span style="color:#a6e22e">key</span>] = <span style="color:#a6e22e">value</span>
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">wg</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>{}
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">20</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
		<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>) {
			<span style="color:#a6e22e">key</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Itoa</span>(<span style="color:#a6e22e">n</span>)
			<span style="color:#a6e22e">set</span>(<span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">n</span>)
			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;k=:%v,v:=%v\n&#34;</span>, <span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">get</span>(<span style="color:#a6e22e">key</span>))
			<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
		}(<span style="color:#a6e22e">i</span>)
	}
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
}
</code></pre></div><p>上面的代码开启少量几个<code>goroutine</code>的时候可能没什么问题，当并发多了之后执行上面的代码就会报<code>fatal error: concurrent map writes</code>错误。</p>
<p>像这种场景下就需要为map加锁来保证并发的安全性了，Go语言的<code>sync</code>包中提供了一个开箱即用的并发安全版map–<code>sync.Map</code>。开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。同时<code>sync.Map</code>内置了诸如<code>Store</code>、<code>Load</code>、<code>LoadOrStore</code>、<code>Delete</code>、<code>Range</code>等操作方法。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">m</span> = <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Map</span>{}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">wg</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>{}
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">20</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
		<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>) {
			<span style="color:#a6e22e">key</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Itoa</span>(<span style="color:#a6e22e">n</span>)
			<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">Store</span>(<span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">n</span>)
			<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">Load</span>(<span style="color:#a6e22e">key</span>)
			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;k=:%v,v:=%v\n&#34;</span>, <span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">value</span>)
			<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
		}(<span style="color:#a6e22e">i</span>)
	}
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
}
</code></pre></div><h2 id="原子操作">原子操作</h2>
<p>代码中的加锁操作因为涉及内核态的上下文切换会比较耗时、代价比较高。针对基本数据类型我们还可以使用原子操作来保证并发安全，因为原子操作是Go语言提供的方法它在用户态就可以完成，因此性能比加锁操作更好。Go语言中原子操作由内置的标准库<code>sync/atomic</code>提供。</p>
<h3 id="atomic包">atomic包</h3>
<table>
<thead>
<tr>
<th style="text-align:center">方法</th>
<th style="text-align:center">解释</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">func LoadInt32(addr *int32) (val int32) func LoadInt64(addr *int64) (val int64) func LoadUint32(addr *uint32) (val uint32) func LoadUint64(addr *uint64) (val uint64) func LoadUintptr(addr *uintptr) (val uintptr) func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)</td>
<td style="text-align:center">读取操作</td>
</tr>
<tr>
<td style="text-align:center">func StoreInt32(addr *int32, val int32) func StoreInt64(addr *int64, val int64) func StoreUint32(addr *uint32, val uint32) func StoreUint64(addr *uint64, val uint64) func StoreUintptr(addr *uintptr, val uintptr) func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)</td>
<td style="text-align:center">写入操作</td>
</tr>
<tr>
<td style="text-align:center">func AddInt32(addr *int32, delta int32) (new int32) func AddInt64(addr *int64, delta int64) (new int64) func AddUint32(addr *uint32, delta uint32) (new uint32) func AddUint64(addr *uint64, delta uint64) (new uint64) func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)</td>
<td style="text-align:center">修改操作</td>
</tr>
<tr>
<td style="text-align:center">func SwapInt32(addr *int32, new int32) (old int32) func SwapInt64(addr *int64, new int64) (old int64) func SwapUint32(addr *uint32, new uint32) (old uint32) func SwapUint64(addr *uint64, new uint64) (old uint64) func SwapUintptr(addr *uintptr, new uintptr) (old uintptr) func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)</td>
<td style="text-align:center">交换操作</td>
</tr>
<tr>
<td style="text-align:center">func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool) func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool) func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool) func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)</td>
<td style="text-align:center">比较并交换操作</td>
</tr>
</tbody>
</table>
<h3 id="示例">示例</h3>
<p>我们填写一个示例来比较下互斥锁和原子操作的性能。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>

<span style="color:#f92672">import</span> (
	<span style="color:#e6db74">&#34;fmt&#34;</span>
	<span style="color:#e6db74">&#34;sync&#34;</span>
	<span style="color:#e6db74">&#34;sync/atomic&#34;</span>
	<span style="color:#e6db74">&#34;time&#34;</span>
)

<span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Counter</span> <span style="color:#66d9ef">interface</span> {
	<span style="color:#a6e22e">Inc</span>()
	<span style="color:#a6e22e">Load</span>() <span style="color:#66d9ef">int64</span>
}

<span style="color:#75715e">// 普通版
</span><span style="color:#75715e"></span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">CommonCounter</span> <span style="color:#66d9ef">struct</span> {
	<span style="color:#a6e22e">counter</span> <span style="color:#66d9ef">int64</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">CommonCounter</span>) <span style="color:#a6e22e">Inc</span>() {
	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">counter</span><span style="color:#f92672">++</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">CommonCounter</span>) <span style="color:#a6e22e">Load</span>() <span style="color:#66d9ef">int64</span> {
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">counter</span>
}

<span style="color:#75715e">// 互斥锁版
</span><span style="color:#75715e"></span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">MutexCounter</span> <span style="color:#66d9ef">struct</span> {
	<span style="color:#a6e22e">counter</span> <span style="color:#66d9ef">int64</span>
	<span style="color:#a6e22e">lock</span>    <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Mutex</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">m</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">MutexCounter</span>) <span style="color:#a6e22e">Inc</span>() {
	<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Lock</span>()
	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Unlock</span>()
	<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">counter</span><span style="color:#f92672">++</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">m</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">MutexCounter</span>) <span style="color:#a6e22e">Load</span>() <span style="color:#66d9ef">int64</span> {
	<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Lock</span>()
	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Unlock</span>()
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">counter</span>
}

<span style="color:#75715e">// 原子操作版
</span><span style="color:#75715e"></span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">AtomicCounter</span> <span style="color:#66d9ef">struct</span> {
	<span style="color:#a6e22e">counter</span> <span style="color:#66d9ef">int64</span>
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">a</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">AtomicCounter</span>) <span style="color:#a6e22e">Inc</span>() {
	<span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">AddInt64</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">a</span>.<span style="color:#a6e22e">counter</span>, <span style="color:#ae81ff">1</span>)
}

<span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">a</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">AtomicCounter</span>) <span style="color:#a6e22e">Load</span>() <span style="color:#66d9ef">int64</span> {
	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">LoadInt64</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">a</span>.<span style="color:#a6e22e">counter</span>)
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">test</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">Counter</span>) {
	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
	<span style="color:#a6e22e">start</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">1000</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
		<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Inc</span>()
			<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
		}()
	}
	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
	<span style="color:#a6e22e">end</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Load</span>(), <span style="color:#a6e22e">end</span>.<span style="color:#a6e22e">Sub</span>(<span style="color:#a6e22e">start</span>))
}

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
	<span style="color:#a6e22e">c1</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">CommonCounter</span>{} <span style="color:#75715e">// 非并发安全
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">test</span>(<span style="color:#a6e22e">c1</span>)
	<span style="color:#a6e22e">c2</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">MutexCounter</span>{} <span style="color:#75715e">// 使用互斥锁实现并发安全
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">test</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">c2</span>)
	<span style="color:#a6e22e">c3</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">AtomicCounter</span>{} <span style="color:#75715e">// 并发安全且比互斥锁效率更高
</span><span style="color:#75715e"></span>	<span style="color:#a6e22e">test</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">c3</span>)
}
</code></pre></div><p><code>atomic</code>包提供了底层的原子级内存操作，对于同步算法的实现很有用。这些函数必须谨慎地保证正确使用。除了某些特殊的底层应用，使用通道或者sync包的函数/类型实现同步更好。</p>
<h1 id="练习题">练习题</h1>
<ol>
<li>
<p>使用</p>
<pre><code>goroutine
</code></pre><p>和</p>
<pre><code>channel
</code></pre><p>实现一个计算int64随机数各位数和的程序。</p>
<ol>
<li>开启一个<code>goroutine</code>循环生成int64类型的随机数，发送到<code>jobChan</code></li>
<li>开启24个<code>goroutine</code>从<code>jobChan</code>中取出随机数计算各位数的和，将结果发送到<code>resultChan</code></li>
<li>主<code>goroutine</code>从<code>resultChan</code>取出结果并打印到终端输出</li>
</ol>
</li>
<li>
<p>为了保证业务代码的执行性能将之前写的日志库改写为异步记录日志方式。</p>
</li>
</ol>

        </div>

        
<div class="post-archive">
    <ul class="post-copyright">
        <li><strong>原文作者：</strong><a rel="author" href="https://luckly.work/">luckly</a></li>
        <li style="word-break:break-all"><strong>原文链接：</strong><a href="https://luckly.work/post/go_basic/go%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%B9%8B%E5%B9%B6%E5%8F%91/">https://luckly.work/post/go_basic/go%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%B9%8B%E5%B9%B6%E5%8F%91/</a></li>
        <li><strong>版权声明：</strong>本作品采用<a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/">知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议</a>进行许可，非商业转载请注明出处（作者，原文链接），商业转载请联系作者获得授权。</li>
    </ul>
</div>
<br/>



        

<div class="post-archive">
    <h2>See Also</h2>
    <ul class="listing">
        
        <li><a href="/post/go_package/strconv/">Go-strconv</a></li>
        
        <li><a href="/post/go_package/time/">Go-time</a></li>
        
        <li><a href="/post/go_basic/go%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%B9%8Bmap/">Go语言基础之map</a></li>
        
        <li><a href="/post/go_basic/go%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%B9%8B%E5%87%BD%E6%95%B0/">Go语言基础之函数</a></li>
        
        <li><a href="/post/go_basic/go%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%B9%8B%E5%88%87%E7%89%87/">Go语言基础之切片</a></li>
        
    </ul>
</div>


        <div class="post-meta meta-tags">
            
            <ul class="clearfix">
                
                <li><a href='/tags/go'>go</a></li>
                
            </ul>
            
        </div>
    </article>
    
    

    
    
    <div class="post bg-white">
      <script src="https://utteranc.es/client.js"
            repo= "https://github.com/ITmxs/repo"
            issue-term="pathname"
            theme="github-light"
            crossorigin="anonymous"
            async>
      </script>
    </div>
    
</div>

                    <footer id="footer">
    <div>
        &copy; 2021 <a href="https://luckly.work/">早起的年轻人 By luckly</a>
        
        | <a rel="nofollow" target="_blank" href="http://beian.miit.gov.cn/">粤ICP备2021号-1</a>
        
    </div>
    <br />
    <div>
        <div class="github-badge">
            <a href="https://juejin.cn/user/3843548384077192" target="_black" rel="nofollow"><span class="badge-subject">Powered by</span><span class="badge-value bg-blue">掘金</span></a>
        </div>
        <div class="github-badge">
            <a href="https://space.bilibili.com/480883651" target="_black"><span class="badge-subject">Design by</span><span class="badge-value bg-brightgreen">早起的年轻人</span></a>
        </div>
        <div class="github-badge">
            <a href="https://cloud.tencent.com/developer/user/6702670" target="_black"><span class="badge-subject">Theme</span><span class="badge-value bg-yellowgreen">云社区</span></a>
        </div>
    </div>
</footer>


    
    <script type="text/javascript">
        window.MathJax = {
            tex2jax: {
                inlineMath: [['$', '$']],
                processEscapes: true
                }
            };
    </script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML' async></script><script src="https://cdn.bootcdn.net/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js"></script>

<a id="rocket" href="#top"></a>
<script type="text/javascript" src='/js/totop.js?v=0.0.0' async=""></script>



    <script type="text/javascript" src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" async></script>




    <script src='/js/douban.js'></script>
    <script src="/js/copy-to-clipboard.js"></script>

                </div>

                <div id="secondary">
    <section class="widget">
        <form id="search" action='https://luckly.work/search/' method="get" accept-charset="utf-8" target="_blank" _lpchecked="1">
      
      <input type="text" name="q" maxlength="20" placeholder="Search">
      <input type="hidden" name="sitesearch" value="https://luckly.work/">
      <button type="submit" class="submit icon-search"></button>
</form>
    </section>
    
    <section class="widget">
        <h3 class="widget-title">最近文章</h3>
<ul class="widget-list">
    
    <li>
        <a href="https://luckly.work/post/nginx/nginx%E6%96%87%E4%BB%B6%E5%86%85%E5%AE%B9/" title="Nginx文件内容">Nginx文件内容</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/nginx/nginx%E6%9E%81%E7%AE%80%E6%95%99%E7%A8%8B/" title="Nginx极简教程">Nginx极简教程</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/nginx/%E5%8D%81%E5%88%86%E9%92%9F%E5%85%A5%E9%97%A8nginx/" title="十分钟入门Nginx">十分钟入门Nginx</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/go/Goland%E8%BF%9C%E7%A8%8B%E5%BC%80%E5%8F%91%E9%85%8D%E7%BD%AE/" title="Goland远程开发配置">Goland远程开发配置</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/%E8%AF%BB%E4%B9%A6/%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90/" title="域名解析">域名解析</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/git/%E6%8F%90%E4%BA%A4%E8%BF%87%E7%A8%8B%E7%9C%81%E7%95%A5%E6%9F%90%E4%BA%9B%E6%96%87%E4%BB%B6/" title="提交过程省略某些文件">提交过程省略某些文件</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/flutter_tips/Flutter_DropdownButton%E7%A4%BA%E4%BE%8B/" title="Flutter_DropdownButton示例">Flutter_DropdownButton示例</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/flutter_tips/Flutter_ExpansionPanelList%E5%92%8CExpansionPanelList.radio%E7%A4%BA%E4%BE%8B/" title="Flutter_ExpansionPanelList和ExpansionPanelList">Flutter_ExpansionPanelList和ExpansionPanelList</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/flutter_tips/Flutter%E5%BE%AE%E4%BF%A1%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E8%B7%B3%E5%9B%9EApp%E6%8C%87%E5%AE%9A%E9%A1%B5%E9%9D%A2/" title="Flutter微信分享链接跳回App指定页面">Flutter微信分享链接跳回App指定页面</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F/%E5%A4%96%E5%8C%85%E5%8F%AF%E8%83%BD%E7%9A%84%E9%97%AE%E9%A2%98/" title="外包可能的问题">外包可能的问题</a>
    </li>
    
</ul>
    </section>

    

    <section class="widget">
        <h3 class="widget-title"><a href='/categories/'>分类</a></h3>
<ul class="widget-list">
    
    <li><a href="https://luckly.work/categories/Flutter/">Flutter (326)</a></li>
    
    <li><a href="https://luckly.work/categories/IT/">IT (2)</a></li>
    
    <li><a href="https://luckly.work/categories/Kotlin/">Kotlin (2)</a></li>
    
    <li><a href="https://luckly.work/categories/Mysql/">Mysql (1)</a></li>
    
    <li><a href="https://luckly.work/categories/nginx/">nginx (1)</a></li>
    
    <li><a href="https://luckly.work/categories/Vue/">Vue (6)</a></li>
    
    <li><a href="https://luckly.work/categories/YouTube%E8%A7%86%E9%A2%91%E4%B8%8B%E8%BD%BD/">YouTube视频下载 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/android/">android (6)</a></li>
    
    <li><a href="https://luckly.work/categories/dart/">dart (96)</a></li>
    
    <li><a href="https://luckly.work/categories/Flutter/">Flutter (28)</a></li>
    
    <li><a href="https://luckly.work/categories/gin/">gin (25)</a></li>
    
    <li><a href="https://luckly.work/categories/git/">git (4)</a></li>
    
    <li><a href="https://luckly.work/categories/Go/">Go (102)</a></li>
    
    <li><a href="https://luckly.work/categories/gorm/">gorm (4)</a></li>
    
    <li><a href="https://luckly.work/categories/grpc/">grpc (1)</a></li>
    
    <li><a href="https://luckly.work/categories/html/">html (3)</a></li>
    
    <li><a href="https://luckly.work/categories/ios/">ios (1)</a></li>
    
    <li><a href="https://luckly.work/categories/linux/">linux (1)</a></li>
    
    <li><a href="https://luckly.work/categories/nginx/">nginx (6)</a></li>
    
    <li><a href="https://luckly.work/categories/python/">python (35)</a></li>
    
    <li><a href="https://luckly.work/categories/read/">读书笔记 (6)</a></li>
    
    <li><a href="https://luckly.work/categories/redis/">redis (2)</a></li>
    
    <li><a href="https://luckly.work/categories/%E4%B8%AA%E4%BA%BA%E8%B5%84%E6%96%99/">个人资料 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E4%B9%A6%E5%8D%95/">书单 (8)</a></li>
    
    <li><a href="https://luckly.work/categories/%E4%B9%A6%E8%AF%84/">书评 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86%E5%B8%88/">信息系统管理师 (19)</a></li>
    
    <li><a href="https://luckly.work/categories/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E5%B8%88/">信息系统项目管理师 (25)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%8E%9F%E5%88%99/">原则 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%8E%9F%E7%94%9F%E9%80%9A%E8%AE%AF/">原生通讯 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%9F%BA%E7%A1%80/">基础 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%A4%8D%E5%88%A9%E6%95%88%E5%BA%94/">复利效应 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B/">安装教程 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%B0%91%E6%9C%89%E4%BA%BA%E8%B5%B0%E7%9A%84%E8%B7%AF/">少有人走的路 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%BF%83%E8%AF%AD/">心语 (3)</a></li>
    
    <li><a href="https://luckly.work/categories/%E6%8F%92%E4%BB%B6/">插件 (2)</a></li>
    
    <li><a href="https://luckly.work/categories/%E6%95%99%E5%AD%A6/">教学 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%8E%8B%E9%98%B3%E6%98%8E/">王阳明 (3)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%94%B5%E5%AD%90%E4%B9%A6/">电子书 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%99%BB%E9%99%86%E8%A1%A8%E5%8D%95/">登陆表单 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%A8%BB%E7%9B%9B%E5%92%8C%E5%A4%AB/">稻盛和夫 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%A9%B7%E7%88%B8%E7%88%B8%E5%AF%8C%E7%88%B8%E7%88%B8/">穷爸爸富爸爸 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%B2%BE%E8%BF%9B/">精进 (3)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%BC%96%E7%A8%8B/">编程 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%99%9A%E5%B9%BB/">虚幻 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%B4%A2%E5%8A%A1%E8%87%AA%E7%94%B1%E4%B9%8B%E8%B7%AF/">财务自由之路 (2)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%B7%91%E6%AD%A5/">跑步 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%B7%AF%E7%94%B1%E4%BC%A0%E5%8F%82/">路由传参 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%B7%AF%E7%BA%BF/">路线 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%BD%AF%E4%BB%B6%E5%AE%9E%E6%96%BD/">软件实施 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E9%98%B3%E6%98%8E%E5%BF%83%E5%AD%A6/">阳明心学 (3)</a></li>
    
    <li><a href="https://luckly.work/categories/%E9%A1%B9%E7%9B%AE/">项目 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E9%AD%85%E5%8A%9B/">魅力 (1)</a></li>
    
</ul>
    </section>

    <section class="widget">
        <h3 class="widget-title"><a href='/tags/'>标签</a></h3>
<div class="tagcloud">
    
    <a href="https://luckly.work/tags/flutter/">flutter</a>
    
    <a href="https://luckly.work/tags/IT/">IT</a>
    
    <a href="https://luckly.work/tags/Kotlin/">Kotlin</a>
    
    <a href="https://luckly.work/tags/Mysql/">Mysql</a>
    
    <a href="https://luckly.work/tags/nginx/">nginx</a>
    
    <a href="https://luckly.work/tags/Vue/">Vue</a>
    
    <a href="https://luckly.work/tags/YouTube%E8%A7%86%E9%A2%91%E4%B8%8B%E8%BD%BD/">YouTube视频下载</a>
    
    <a href="https://luckly.work/tags/android/">android</a>
    
    <a href="https://luckly.work/tags/dart/">dart</a>
    
    <a href="https://luckly.work/tags/flutter/">flutter</a>
    
    <a href="https://luckly.work/tags/gin/">gin</a>
    
    <a href="https://luckly.work/tags/git/">git</a>
    
    <a href="https://luckly.work/tags/go/">go</a>
    
    <a href="https://luckly.work/tags/gorm/">gorm</a>
    
    <a href="https://luckly.work/tags/grpc/">grpc</a>
    
    <a href="https://luckly.work/tags/html/">html</a>
    
    <a href="https://luckly.work/tags/ios/">ios</a>
    
    <a href="https://luckly.work/tags/linux/">linux</a>
    
    <a href="https://luckly.work/tags/nginx/">nginx</a>
    
    <a href="https://luckly.work/tags/python/">python</a>
    
    <a href="https://luckly.work/tags/redis/">redis</a>
    
    <a href="https://luckly.work/tags/%E4%B8%AA%E4%BA%BA%E8%B5%84%E6%96%99/">个人资料</a>
    
    <a href="https://luckly.work/tags/%E4%B9%A6%E5%8D%95/">书单</a>
    
    <a href="https://luckly.work/tags/%E4%B9%A6%E8%AF%84/">书评</a>
    
    <a href="https://luckly.work/tags/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86%E5%B8%88/">信息系统管理师</a>
    
    <a href="https://luckly.work/tags/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E5%B8%88/">信息系统项目管理师</a>
    
    <a href="https://luckly.work/tags/%E5%85%A5%E9%97%A8/">入门</a>
    
    <a href="https://luckly.work/tags/%E5%8E%9F%E5%88%99/">原则</a>
    
    <a href="https://luckly.work/tags/%E5%8E%9F%E7%94%9F%E9%80%9A%E8%AE%AF/">原生通讯</a>
    
    <a href="https://luckly.work/tags/%E5%9F%BA%E7%A1%80/">基础</a>
    
    <a href="https://luckly.work/tags/%E5%A4%8D%E5%88%A9%E6%95%88%E5%BA%94/">复利效应</a>
    
    <a href="https://luckly.work/tags/%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B/">安装教程</a>
    
    <a href="https://luckly.work/tags/%E5%B0%91%E6%9C%89%E4%BA%BA%E8%B5%B0%E7%9A%84%E8%B7%AF/">少有人走的路</a>
    
    <a href="https://luckly.work/tags/%E5%BF%83%E8%AF%AD/">心语</a>
    
    <a href="https://luckly.work/tags/%E6%8F%92%E4%BB%B6/">插件</a>
    
    <a href="https://luckly.work/tags/%E6%95%99%E5%AD%A6/">教学</a>
    
    <a href="https://luckly.work/tags/%E7%8E%8B%E9%98%B3%E6%98%8E/">王阳明</a>
    
    <a href="https://luckly.work/tags/%E7%94%B5%E5%AD%90%E4%B9%A6/">电子书</a>
    
    <a href="https://luckly.work/tags/%E7%99%BB%E9%99%86%E8%A1%A8%E5%8D%95/">登陆表单</a>
    
    <a href="https://luckly.work/tags/%E7%A8%BB%E7%9B%9B%E5%92%8C%E5%A4%AB/">稻盛和夫</a>
    
    <a href="https://luckly.work/tags/%E7%A9%B7%E7%88%B8%E7%88%B8%E5%AF%8C%E7%88%B8%E7%88%B8/">穷爸爸富爸爸</a>
    
    <a href="https://luckly.work/tags/%E7%B2%BE%E8%BF%9B/">精进</a>
    
    <a href="https://luckly.work/tags/%E7%BC%96%E7%A8%8B/">编程</a>
    
    <a href="https://luckly.work/tags/%E8%99%9A%E5%B9%BB/">虚幻</a>
    
    <a href="https://luckly.work/tags/%E8%AF%97/">诗</a>
    
    <a href="https://luckly.work/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/">读书笔记</a>
    
    <a href="https://luckly.work/tags/%E8%B4%A2%E5%8A%A1%E8%87%AA%E7%94%B1%E4%B9%8B%E8%B7%AF/">财务自由之路</a>
    
    <a href="https://luckly.work/tags/%E8%B7%91%E6%AD%A5/">跑步</a>
    
    <a href="https://luckly.work/tags/%E8%B7%AF%E7%94%B1%E4%BC%A0%E5%8F%82/">路由传参</a>
    
    <a href="https://luckly.work/tags/%E8%B7%AF%E7%BA%BF/">路线</a>
    
    <a href="https://luckly.work/tags/%E8%BD%AF%E4%BB%B6%E5%AE%9E%E6%96%BD/">软件实施</a>
    
    <a href="https://luckly.work/tags/%E9%80%9A%E8%AE%AF%E5%BD%95/">通讯录</a>
    
    <a href="https://luckly.work/tags/%E9%98%B3%E6%98%8E%E5%BF%83%E5%AD%A6/">阳明心学</a>
    
    <a href="https://luckly.work/tags/%E9%A1%B9%E7%9B%AE/">项目</a>
    
    <a href="https://luckly.work/tags/%E9%AD%85%E5%8A%9B/">魅力</a>
    
</div>
    </section>

    
<section class="widget">
    <h3 class="widget-title">友情链接</h3>
    <ul class="widget-list">
        
        <li>
            <a target="_blank" href="http://www.topgoer.com/" title="枯藤">枯藤</a>
        </li>
        
        <li>
            <a target="_blank" href="https://gorm.cn/zh_CN/docs/index.html" title="gorm">gorm</a>
        </li>
        
        <li>
            <a target="_blank" href="https://docs.python.org/zh-cn/3/tutorial/index.html" title="python">python</a>
        </li>
        
        <li>
            <a target="_blank" href="https://www.liwenzhou.com/" title="李文周">李文周的博客</a>
        </li>
        
        <li>
            <a target="_blank" href="http://www.xbzweb.com/" title="小包子的博客">小包子的博客</a>
        </li>
        
        <li>
            <a target="_blank" href="https://www.flysnow.org/" title="飞雪无情的博客">飞雪无情的博客</a>
        </li>
        
        <li>
            <a target="_blank" href="https://sliverhorn.com/" title="sliverhorn的博客">sliverhorn的博客</a>
        </li>
        
        <li>
            <a target="_blank" href="http://yuedu.baidu.com/ebook/14a722970740be1e640e9a3e" title="Android Gradle权威指南">Android Gradle权威指南</a>
        </li>
        
        <li>
            <a target="_blank" href="https://gesdh.cn/" title="小格子">格子导航</a>
        </li>
        
        <li>
            <a target="_blank" href="https://itachi.xyz/" title="阿林">itachi&#39;s Blog</a>
        </li>
        
        <li>
            <a target="_blank" href="https://darjun.github.io/" title="大俊">大俊Blog</a>
        </li>
        
        <li>
            <a target="_blank" href="https://geektutu.com/post/quick-golang.html" title="极客兔兔">极客兔兔Blog</a>
        </li>
        
        <li>
            <a target="_blank" href="http://zxfcumtcs.github.io/" title="赵雪峰">雪峰Blog</a>
        </li>
        
    </ul>
</section>


    <section class="widget">
        <h3 class="widget-title">其它</h3>
        <ul class="widget-list">
            <li><a href="https://luckly.work/index.xml">文章 RSS</a></li>
        </ul>
    </section>
</div>
            </div>
        </div>
    </div>
</body>

</html>